package com.xjd.edu.common.feign;

import com.xjd.edu.common.admin.core.constant.SecurityConstants;
import com.xjd.edu.common.admin.core.utils.ServletUtils;
import com.xjd.edu.common.admin.core.utils.ip.IpUtils;
import com.xjd.edu.common.utils.StringUtils;
import com.xjd.edu.common.utils.UuidUtil;
import feign.Feign;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static com.xjd.edu.common.feign.FeignCountMeterEnvironmentChangeEventListener.METER_ID_NAME;
import static com.xjd.edu.common.constant.HeaderConstant.X_REQUEST_ID;
import static com.xjd.edu.common.constant.HeaderConstant.X_REQUEST_URI;

/**
 * 日志拦截器（OpenFeign）
 * https://docs.spring.io/spring-cloud-openfeign/docs/2.2.8.RELEASE/reference/html/
 *
 * @author wangyu
 * @date 2024/06/07 18:48
 */
@Slf4j
@Component
@ConditionalOnClass(Feign.class)
@ConditionalOnProperty(name = "feign.logging.enable", matchIfMissing = true)
public class LoggingRequestInterceptor implements RequestInterceptor {

    @Autowired
    private StandardEnvironment environment;

    @Autowired
    private MeterRegistry registry;

    @Override
    public void apply(RequestTemplate rt) {
        // 将MDC中的 X-Request-Id 添加到请求消息头
        String requestId = MDC.get(X_REQUEST_ID);
        MDC.MDCCloseable mdc = null;
        // 如果上游不传则自己生成（局部可追溯好过无法追溯）
        if (StringUtils.isBlank(requestId)) {
            requestId = "feign_" + UuidUtil.uuid();
            mdc = MDC.putCloseable(X_REQUEST_ID, requestId);
        }

        // 向服务提供者传递 X-Request-Id 消息头
        rt.header(X_REQUEST_ID, requestId);

        // 向服务提供者传递 upstream 信息
        final String upstream = MDC.get(X_REQUEST_URI);
        if (StringUtils.isNotBlank(upstream)) {
            rt.header(X_REQUEST_URI, upstream);
        }

        // 业务属性向下游传递
        HttpServletRequest httpServletRequest = ServletUtils.getRequest();
        if (StringUtils.isNotNull(httpServletRequest)) {
            Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
            // 传递用户信息请求头，防止丢失
            String userId = headers.get(SecurityConstants.DETAILS_USER_ID);
            if (StringUtils.isNotEmpty(userId)) {
                rt.header(SecurityConstants.DETAILS_USER_ID, userId);
            }
            String userKey = headers.get(SecurityConstants.USER_KEY);
            if (StringUtils.isNotEmpty(userKey)) {
                rt.header(SecurityConstants.USER_KEY, userKey);
            }
            String userName = headers.get(SecurityConstants.DETAILS_USERNAME);
            if (StringUtils.isNotEmpty(userName)) {
                rt.header(SecurityConstants.DETAILS_USERNAME, userName);
            }
            String authentication = headers.get(SecurityConstants.AUTHORIZATION_HEADER);
            if (StringUtils.isNotEmpty(authentication)) {
                rt.header(SecurityConstants.AUTHORIZATION_HEADER, authentication);
            }

            // 配置客户端IP
            rt.header("X-Forwarded-For", IpUtils.getIpAddr());
        }

        // 记录调用日志
        if (log.isDebugEnabled()) {
            log.debug("$FEIGN request: {}", rt.url());
        }

        // 解决串号的问题，这里实际只对自生成的串号有作用，本质上Feign拦截器是有一个前置拦截器不能真在这个里把MDC清空掉，否则后面的日志就没有串号了
        if (mdc != null) {
            mdc.close();
        }

        // 生成计数统计
        try {
            this.metrics(upstream, rt.path());
        } catch (Exception e) {
            log.error("请求 {} -> {} 监控计数出错", upstream, rt.path(), e);
        }
    }

    /**
     * 记录请求上下游监控计数
     *
     * @param from 请求上游
     * @param to   请求下游
     */
    private void metrics(String from, String to) {

        if (from == null || to == null) {
            return;
        }

        // 执行计数逻辑
        Counter.builder(METER_ID_NAME)
                .tag("from", from)
                .tag("to", to)
                .register(registry)
                .increment();
    }

}
